The modifications performed by the expander are tree transformations that must
be applied to those Ada constructs that do not have a close equivalent in C,
such as allocators, aggregates, tagged types and dynamic dispatching,
and all aspects of the tasking. The expansion phase also simplifies some
aspects of semantic analysis which are awkward to perform strictly in one
pass, eg. the correct handling of the private part of a package declaration.
The most important expansions are the following;
- Construction of initialization procedures for record and array types,
and invocation of these procedures for each object of such a type. This is
also done for tasks and protected objects.
- Generic instantiation. Instantiation is always done in-line, so that
declaration and body of the instance are inserted into the AST at the point
of instantiation.
- All tasking operations are transformed into calls to subprograms in
the run-time system. The recursive mechanisms of GNAT are particularly useful
here. For example, consider operations on the attribute <#467#>COUNT<#467#>. The
run-time holds the specification of a run-time function that examines the
corresponding queue. Rather than including the details of such a function
in the compiler proper, the run-time package is analyzed by the compiler as
if it had appeared in the context clause of the current compilation. If the
function is subject to an <#468#>INLINE<#468#> pragma, the compiler can perform the
inlining as well, without forcing the compiler to have detailed information
about the run-time, and without affecting code quality. Such flexibility
cannot be achieved with a more conventional compiler organization. Because
of the speed of the compiler, the cost of this approach in terms of space
and time is comparable or cheaper than the conventional approach.